gsk: Add the ability to create fallback renderers
authorEmmanuele Bassi <ebassi@gnome.org>
Thu, 25 Aug 2016 10:31:56 +0000 (11:31 +0100)
committerEmmanuele Bassi <ebassi@gnome.org>
Tue, 18 Oct 2016 10:49:16 +0000 (11:49 +0100)
While porting GTK to GskRenderer we noticed that the current fallback
code for widgets using Cairo to draw is not enough to cover all the
possible cases.

For instance, if a container widget still uses GtkWidget::draw to render
its children, and at least one of them has been ported to using render
nodes instead, the container won't know how to draw it.

For this reason we want to provide to layers above GSK the ability to
create a "fallback" renderer instance, created using a "parent"
GskRenderer instance, but using a Cairo context as the rendering target
instead of a GdkDrawingContext.

GTK will use this inside the gtk_widget_draw() implementation, if a
widget implements GtkWidgetClass.get_render_node().

gsk/gskcairorenderer.c
gsk/gskglrenderer.c
gsk/gskrenderer.c
gsk/gskrenderer.h
gsk/gskrendererprivate.h
gtk/gtkwidget.c

index 3fa2ebec076aa2cfcdd4f3dff955c87a38cd2302..281587d9581e945e25c93d85de62ea45f2ed8f4e 100644 (file)
@@ -135,7 +135,11 @@ gsk_cairo_renderer_render (GskRenderer   *renderer,
   GdkDrawingContext *context = gsk_renderer_get_drawing_context (renderer);
   cairo_t *cr;
 
-  cr = gdk_drawing_context_get_cairo_context (context);
+  if (context != NULL)
+    cr = gdk_drawing_context_get_cairo_context (context);
+  else
+    cr = gsk_renderer_get_cairo_context (renderer);
+
   if (cr == NULL)
     return;
 
index 373f38990745b9729e0f690b1e998630162defe2..c4eec609d34f0d2e850524d03c282dc8f7eebaa4 100644 (file)
@@ -920,9 +920,17 @@ out:
     GdkWindow *window;
     cairo_t *cr;
 
-    /* XXX: Add GdkDrawingContext API */
-    cr = gdk_drawing_context_get_cairo_context (context);
-    window = gdk_drawing_context_get_window (context);
+    if (context != NULL)
+      {
+        /* XXX: Add GdkDrawingContext API */
+        cr = gdk_drawing_context_get_cairo_context (context);
+        window = gdk_drawing_context_get_window (context);
+      }
+    else
+      {
+        cr = gsk_renderer_get_cairo_context (renderer);
+        window = gsk_renderer_get_window (renderer);
+      }
 
     gdk_cairo_draw_from_gl (cr, window,
                             self->texture_id,
index ebdb1a4b28d7e5e4e950dd11b53753a322edbdea..d77bfe1ed38a1ef00f2fc39913891cf60c4bb429 100644 (file)
@@ -37,6 +37,7 @@
 
 #include "gskrendererprivate.h"
 
+#include "gskcairorendererprivate.h"
 #include "gskdebugprivate.h"
 #include "gskglrendererprivate.h"
 #include "gskprofilerprivate.h"
@@ -68,6 +69,7 @@ typedef struct
   GdkDrawingContext *drawing_context;
   GskRenderNode *root_node;
   GdkDisplay *display;
+  cairo_t *cairo_context;
 
   GskProfiler *profiler;
 
@@ -121,6 +123,7 @@ gsk_renderer_dispose (GObject *gobject)
 
   gsk_renderer_unrealize (self);
 
+  g_clear_pointer (&priv->cairo_context, cairo_destroy);
   g_clear_object (&priv->window);
   g_clear_object (&priv->display);
 
@@ -603,6 +606,16 @@ gsk_renderer_render (GskRenderer       *renderer,
 
   if (context != NULL)
     priv->drawing_context = g_object_ref (context);
+  else
+    {
+      if (priv->cairo_context == NULL)
+        {
+          g_critical ("The given GskRenderer instance was not created using "
+                      "gsk_renderer_create_fallback(), but you forgot to pass "
+                      "a GdkDrawingContext.");
+          return;
+        }
+    }
 
   priv->root_node = gsk_render_node_ref (root);
   gsk_render_node_make_immutable (priv->root_node);
@@ -695,7 +708,7 @@ gsk_renderer_get_for_display (GdkDisplay *display)
     }
 
   if (use_software[0] != '0')
-    return NULL;
+    return g_object_new (GSK_TYPE_CAIRO_RENDERER, "display", display, NULL);
 
 #ifdef GDK_WINDOWING_X11
   if (GDK_IS_X11_DISPLAY (display))
@@ -707,7 +720,7 @@ gsk_renderer_get_for_display (GdkDisplay *display)
     renderer_type = GSK_TYPE_GL_RENDERER;
   else
 #endif
-    return NULL;
+    renderer_type = GSK_TYPE_CAIRO_RENDERER;
 
   GSK_NOTE (RENDERER, g_print ("Creating renderer of type '%s' for display '%s'\n",
                                g_type_name (renderer_type),
@@ -717,3 +730,65 @@ gsk_renderer_get_for_display (GdkDisplay *display)
 
   return g_object_new (renderer_type, "display", display, NULL);
 }
+
+static void
+gsk_renderer_set_cairo_context (GskRenderer *renderer,
+                                cairo_t     *cr)
+{
+  GskRendererPrivate *priv = gsk_renderer_get_instance_private (renderer);
+
+  g_clear_pointer (&priv->cairo_context, cairo_destroy);
+
+  if (cr != NULL)
+    priv->cairo_context = cairo_reference (cr);
+}
+
+cairo_t *
+gsk_renderer_get_cairo_context (GskRenderer *renderer)
+{
+  GskRendererPrivate *priv = gsk_renderer_get_instance_private (renderer);
+
+  return priv->cairo_context;
+}
+
+/**
+ * gsk_renderer_create_fallback:
+ * @renderer: a #GskRenderer
+ * @viewport: the viewport for the fallback renderer
+ * @cr: a Cairo context
+ *
+ * Creates a fallback #GskRenderer using the same display and window of
+ * the given @renderer, and instructs it to render to a given Cairo
+ * context.
+ *
+ * Typically, you'll use this function to implement fallback rendering
+ * of #GskRenderNodes on an intermediate Cairo context, instead of using
+ * the drawing context associated to a #GdkWindow's rendering buffer.
+ *
+ * Returns: (transfer full): a newly created fallback #GskRenderer instance
+ *
+ * Since: 3.22
+ */
+GskRenderer *
+gsk_renderer_create_fallback (GskRenderer           *renderer,
+                              const graphene_rect_t *viewport,
+                              cairo_t               *cr)
+{
+  GskRendererPrivate *priv = gsk_renderer_get_instance_private (renderer);
+  GskRenderer *res;
+
+  g_return_val_if_fail (GSK_IS_RENDERER (renderer), NULL);
+  g_return_val_if_fail (cr != NULL, NULL);
+
+  res = g_object_new (GSK_TYPE_CAIRO_RENDERER,
+                      "display", priv->display,
+                      "window", priv->window,
+                      "scale-factor", priv->scale_factor,
+                      "viewport", viewport,
+                      NULL);
+
+  gsk_renderer_set_cairo_context (res, cr);
+  gsk_renderer_realize (res);
+
+  return res;
+}
index 33dd958b7ba26ad228a1e0cf2e3b055228e9176f..4c8fc58892e3ab49489278b428ff9f28b006e79c 100644 (file)
@@ -70,6 +70,11 @@ void                    gsk_renderer_unrealize                  (GskRenderer
 GDK_AVAILABLE_IN_3_22
 GskRenderNode *         gsk_renderer_create_render_node         (GskRenderer             *renderer);
 
+GDK_AVAILABLE_IN_3_22
+GskRenderer *           gsk_renderer_create_fallback            (GskRenderer             *renderer,
+                                                                 const graphene_rect_t   *viewport,
+                                                                 cairo_t                 *cr);
+
 GDK_AVAILABLE_IN_3_22
 void                    gsk_renderer_render                     (GskRenderer             *renderer,
                                                                  GskRenderNode           *root,
index 12671886f08c7302831cebef1a4933d5ff4db180..d702ae5a7835fbd3aeabc1cd8a154bd10a44322b 100644 (file)
@@ -48,6 +48,7 @@ gboolean gsk_renderer_is_realized (GskRenderer *renderer);
 
 GskRenderNode *         gsk_renderer_get_root_node              (GskRenderer *renderer);
 GdkDrawingContext *     gsk_renderer_get_drawing_context        (GskRenderer *renderer);
+cairo_t *               gsk_renderer_get_cairo_context          (GskRenderer *renderer);
 
 GskProfiler *           gsk_renderer_get_profiler               (GskRenderer *renderer);
 
index 1f6b06db94bd4fb9a6a489613b5db8ae33897000..ad54378617bdfa13e1a54ea5f2ad59f35f10b673 100644 (file)
@@ -6354,6 +6354,18 @@ gtk_cairo_set_marked_for_draw (cairo_t  *cr,
     cairo_set_user_data (cr, &mark_for_draw_key, NULL, NULL);
 }
 
+static GskRenderer *
+gtk_widget_get_renderer (GtkWidget *widget)
+{
+  GtkWidget *toplevel;
+
+  toplevel = _gtk_widget_get_toplevel (widget);
+  if (_gtk_widget_is_toplevel (toplevel))
+    return gtk_window_get_renderer (GTK_WINDOW (toplevel));
+
+  return NULL;
+}
+
 /**
  * gtk_cairo_should_draw_window:
  * @cr: a cairo context
@@ -6421,6 +6433,7 @@ gtk_widget_draw_internal (GtkWidget *widget,
 
   if (gdk_cairo_get_clip_rectangle (cr, NULL))
     {
+      GtkWidgetClass *widget_class = GTK_WIDGET_GET_CLASS (widget);
       GdkWindow *event_window = NULL;
       gboolean result;
       gboolean push_group;
@@ -6453,17 +6466,46 @@ gtk_widget_draw_internal (GtkWidget *widget,
         g_warning ("%s %p is drawn without a current allocation. This should not happen.", G_OBJECT_TYPE_NAME (widget), widget);
 #endif
 
-      if (g_signal_has_handler_pending (widget, widget_signals[DRAW], 0, FALSE))
+      /* If the widget uses GSK render nodes then we need a fallback path to
+       * render on the Cairo context; otherwise we just go through the old
+       * GtkWidget::draw path
+       */
+      if (widget_class->get_render_node != NULL)
         {
-          g_signal_emit (widget, widget_signals[DRAW],
-                         0, cr,
-                         &result);
+          GskRenderer *renderer = gtk_widget_get_renderer (widget);
+          GskRenderer *fallback;
+          graphene_rect_t viewport;
+          GskRenderNode *node;
+
+          graphene_rect_init (&viewport,
+                              widget->priv->clip.x,
+                              widget->priv->clip.y,
+                              widget->priv->clip.width,
+                              widget->priv->clip.height);
+          fallback = gsk_renderer_create_fallback (renderer, &viewport, cr);
+          node = gtk_widget_get_render_node (widget, fallback);
+          if (node != NULL)
+            {
+              gsk_renderer_render (fallback, node, NULL);
+              gsk_render_node_unref (node);
+            }
+
+          g_object_unref (fallback);
         }
-      else if (GTK_WIDGET_GET_CLASS (widget)->draw)
+      else
         {
-          cairo_save (cr);
-          GTK_WIDGET_GET_CLASS (widget)->draw (widget, cr);
-          cairo_restore (cr);
+          if (g_signal_has_handler_pending (widget, widget_signals[DRAW], 0, FALSE))
+            {
+              g_signal_emit (widget, widget_signals[DRAW],
+                             0, cr,
+                             &result);
+            }
+          else if (GTK_WIDGET_GET_CLASS (widget)->draw)
+            {
+              cairo_save (cr);
+              GTK_WIDGET_GET_CLASS (widget)->draw (widget, cr);
+              cairo_restore (cr);
+            }
         }
 
 #ifdef G_ENABLE_DEBUG
@@ -15827,18 +15869,6 @@ gtk_widget_reset_controllers (GtkWidget *widget)
     }
 }
 
-GskRenderer *
-gtk_widget_get_renderer (GtkWidget *widget)
-{
-  GtkWidget *toplevel;
-
-  toplevel = _gtk_widget_get_toplevel (widget);
-  if (_gtk_widget_is_toplevel (toplevel))
-    return gtk_window_get_renderer (GTK_WINDOW (toplevel));
-
-  return NULL;
-}
-
 GskRenderNode *
 gtk_widget_create_render_node (GtkWidget   *widget,
                                GskRenderer *renderer,